package com.bondar.arraylist;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.RandomAccess;

import com.bondar.exceptions.MyIndexOutOfBoundsException;
import com.bondar.interfaces.MyList;

public class MyArrayList implements MyList, RandomAccess {
	private Object[] data;
	private int capacity;
	private int size;

	/**
	 * Default constructor. Constructs an empty list with an initial capacity of
	 * ten (10)
	 */
	public MyArrayList() {
		this(10);
	}

	/**
	 * Copy constructor. Create new MyArrayList from list with 2*capacity of
	 * list
	 * 
	 * @param list
	 */
	public MyArrayList(MyList list) {
		capacity = list.size() * 2;

		for (int i = 0; i < list.size(); i++) {
			data[i] = list.get(i);
		}
	}

	/**
	 * @param capacity
	 *            initial size of list
	 */
	public MyArrayList(int initialCapacity) {
		this.capacity = initialCapacity;
		data = new Object[capacity];
		size = 0;
	}

	/**
	 * This method insert element at the end of the list.<br>
	 * If initialCapasity not enough -> list should increase size
	 * 
	 * @param obj
	 */
	@Override
	public void add(Object obj) {
		ensureCapacity(size);

		data[size++] = obj;
	}

	/**
	 * This method insert the object at specified position,<br>
	 * shifting the remaining elements
	 * 
	 * @param index
	 *            position for. If index > data.length-1 or index < 0<br>
	 *            throw {@link IndexOutOfBoundsException}
	 * @param obj
	 *            {@link Object}
	 */
	@Override
	public void add(int index, Object obj) throws MyIndexOutOfBoundsException {
		if (index > size || index < 0) {
			throw new MyIndexOutOfBoundsException();
		}
		
		ensureCapacity(size);
		shift(index, 1);
		data[index] = obj;

		++size;
	}

	/**
	 * This method appends all of the elements to the end of this list
	 * 
	 * @param arr
	 *            array of {@link Object}. If arr == null<br>
	 *            throw {@link IllegalArgumentException}
	 */
	@Override
	public void addAll(Object[] arr) {
		if (arr == null) {
			throw new IllegalArgumentException();
		}
		
		ensureCapacity(size + arr.length);

		for (int i = 0; i < arr.length; i++) {
			data[i + size] = arr[i];
		}

		size += arr.length;
	}

	/**
	 * This method inserts all of the elements into this list,<br>
	 * starting at the specified position
	 * 
	 * @param index
	 *            specified position. If list is empty and index > 0<br>
	 *            throw {@link IndexOutOfBoundsException}
	 * @param arr
	 *            array of {@link Object}. If arr == null<br>
	 *            throw {@link IllegalArgumentException}
	 */
	@Override
	public void addAll(int index, Object[] arr)
			throws MyIndexOutOfBoundsException {
		if (size == 0 && index > 0) {
			throw new MyIndexOutOfBoundsException();
		}
		if (arr == null) {
			throw new IllegalArgumentException();
		}
		ensureCapacity(size + arr.length);

		shift(index, arr.length);

		for (int i = 0; i < arr.length; i++) {
			data[index + i] = arr[i];
		}

		size += arr.length;
	}

	/**
	 * This method increases the capacity of this ArrayList instance,<br>
	 * if necessary, to ensure that it can hold at least <br>
	 * the number of elements specified by the minimum <br>
	 * capacity argument.
	 * 
	 * @param minCapacity
	 */
	public void ensureCapacity(int minCapacity) {
		int newCapacitySize = minCapacity;
		if (newCapacitySize <= capacity) {
			// choose new capacity
			newCapacitySize = capacity * 2;
		}

		// resize
		Object[] copy = new Object[newCapacitySize];

		for (int i = 0; i < data.length; i++) {
			copy[i] = data[i];
		}

		data = copy;
		capacity = newCapacitySize;
	}

	/**
	 * This method return object at specified position
	 * 
	 * @param index
	 *            position. If index > data.length-1 or index < 0<br>
	 *            throw {@link IndexOutOfBoundsException}
	 * @return object at specified position
	 */
	@Override
	public Object get(int index) throws MyIndexOutOfBoundsException {
		if (index > size - 1 || index < 0) {
			throw new MyIndexOutOfBoundsException();
		}
		return data[index];
	}

	/**
	 * This method remove object from list at specified position. After remove
	 * size of the list = size-1
	 * 
	 * @param index
	 *            position. If index > data.length-1 or index < 0<br>
	 *            throw {@link IndexOutOfBoundsException}
	 * @return removed object
	 */
	@Override
	public Object remove(int index) throws MyIndexOutOfBoundsException {
		if (index > size - 1 || index < 0) {
			throw new MyIndexOutOfBoundsException();
		}
		Object removeObj = data[index];
		// shrink list
		if (size > 0 && size == capacity / 4) {
			int newSize = capacity / 2;
			Object[] copy = new Object[newSize];

			for (int i = 0; i < copy.length; i++) {
				copy[i] = data[i];
			}

			data = copy;
			capacity = newSize;
		}

		// shift
		for (int i = index; i < size-1; i++) {
			data[i] = data[i + 1];
		}

		--size;
		return removeObj;
	}

	/**
	 * This method change value of element at specified position
	 * 
	 * @param index
	 *            position. If index > data.length-1 or index < 0<br>
	 *            throw {@link IndexOutOfBoundsException}
	 * @param element
	 */
	@Override
	public void set(int index, Object element)
			throws MyIndexOutOfBoundsException {
		if (index > size - 1 || index < 0) {
			throw new MyIndexOutOfBoundsException();
		}
		data[index] = element;
	}

	public int size() {
		return size;
	}

	public int capacity() {
		return capacity;
	}

	private void shift(int index, int offset) {
		for (int i = size + offset - 1, j = 0; i >= index + offset; i--, j++) {
			data[i] = data[size - j - 1];
		}
	}

	@Override
	public String toString() {
		if (size == 0) {
			return "[]";
		}
		StringBuilder sb = new StringBuilder("[");

		for (int i = 0; i < size - 1; i++) {
			sb.append(data[i] + ", ");
		}

		return sb.append(data[size - 1]).append("]").toString();
	}

	@Override
	public int indexOf(Object obj) {
		for (int i = 0; i < size; i++) {
			if (data[i] == null) {
				if (obj == null) {
					return i;
				}
			} else {
				if (data[i].equals(obj) == true) {
					return i;
				}
			}
		}

		return -1;
	}

	@Override
	public Object[] toArray() {
		Object[] array = new Object[size];
		
		for (int i = 0; i < size; i++) {
			array[i] = data[i];
		}

		return array;
	}

	@Override
	public void clear() {
		for (int i = 0; i < size; i++) {
			data[i] = null;
		}

		size = 0;
	}

	@Override
	public boolean isEmpty() {
		return size == 0;
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Iterator iterator() {
		return new Iterator() {
			int index = 0;

			@Override
			public boolean hasNext() {
				return index < size;
			}

			@Override
			public Object next() {
				if (index < 0) {
					throw new NoSuchElementException();
				}
				return data[index++];
			}

			@Override
			public void remove() {
			}

		};
	}

	/**
	 * Trim capacity to real size of MyArrayList
	 */
	public void trimToSize() {
		capacity = (capacity > size) ? size : capacity;
	}

}
